﻿#include "QCefUtil.h"

CefString QStrToCefStr(const QString & str)
{
    return CefString(str.toStdString());
}

QString CefStrToQStr(const CefString & str)
{
    return QString::fromStdString(str.ToString());
}

QBitmap CefImageToQBitmap(CefRefPtr<CefImage> image)
{
    QBitmap bitmap;

    if (image != nullptr)
    {
        int width = 0;
        int height = 0;

        CefRefPtr<CefBinaryValue> data = image->GetAsPNG(1.0, true, width, height);
        int bufsize = (int)data->GetSize();
        if (bufsize > 0)
        {
            QByteArray buffer(bufsize + 4, char(0));
            data->GetData(buffer.data(), bufsize, 0);

            bitmap.loadFromData(buffer);
        }
    }

    return bitmap;
}

QIcon CefImageToQIcon(CefRefPtr<CefImage> image)
{
    return QIcon(CefImageToQBitmap(image));
}


QRect CefRectToQRect(const CefRect & rect)
{
    return QRect(rect.x, rect.y, rect.width, rect.height);
}

CefRect QRectToCefRect(const QRect & rect)
{
    return CefRect(rect.x(), rect.y(), rect.width(), rect.height());
}


QRegion CefRegionToQRegion(const CefRenderHandler::RectList & rects)
{
    QRegion region;

    for (auto & rect : rects)
    {
        region += QRect{ rect.x, rect.y, rect.width, rect.height };
    }

    return region;
}

CefRenderHandler::RectList QRegionToCefRegion(const QRegion & rects)
{
    CefRenderHandler::RectList region;

    for (auto & rect : rects)
    {
        region.push_back(CefRect(rect.x(), rect.y(), rect.width(), rect.height()));
    }

    return region;
}



void CefValueToQVariant(QVariant & variant, const CefRefPtr<CefValue> & cefValue)
{
    if (cefValue == nullptr)
    {
        qWarning() << "Invalid arguments";
        return;
    }

    auto type = cefValue->GetType();
    switch (type)
    {
    case CefValueType::VTYPE_INVALID:
    case CefValueType::VTYPE_NULL:
    {
        variant.setValue(QVariant());
    }
    break;
    case CefValueType::VTYPE_BOOL:
    {
        variant.setValue<bool>(cefValue->GetBool());
    }
    break;
    case CefValueType::VTYPE_INT:
    {
        variant.setValue<int>(cefValue->GetInt());
    }
    break;
    case CefValueType::VTYPE_DOUBLE:
    {
        variant.setValue<double>(cefValue->GetDouble());
    }
    break;
    case CefValueType::VTYPE_STRING:
    {
#if defined(CEF_STRING_TYPE_UTF16)
        QString qStr = QString::fromUtf16((char16_t*)cefValue->GetString().c_str());
#elif defined(CEF_STRING_TYPE_UTF8)
        QString qStr = QString::fromUtf8(cefValue->GetString().c_str());
#elif defined(CEF_STRING_TYPE_WIDE)
        QString qStr = QString::fromWCharArray(cefValue->GetString().c_str());
#endif
        variant.setValue(qStr);
    }
    break;
    case CefValueType::VTYPE_BINARY:
    {
        auto cData = cefValue->GetBinary();
        auto cLen = cData->GetSize();
        QByteArray qData((int)(cLen), 0);
        cefValue->GetBinary()->GetData(qData.data(), qData.size(), 0);
        variant.setValue(qData);
    }
    break;
    case CefValueType::VTYPE_DICTIONARY:
    {
        auto cDict = cefValue->GetDictionary();
        CefDictionaryValue::KeyList cKeys;
        if (!cDict->GetKeys(cKeys))
        {
            qWarning() << "Failed to get cef dictionary keys";
        }
        QVariantMap qDict;
        for (auto& key : cKeys)
        {
            auto cVal = cDict->GetValue(key);
            QVariant qVal;
            CefValueToQVariant(qVal, cVal);
#if defined(CEF_STRING_TYPE_UTF16)
            QString qKey = QString::fromUtf16((char16_t*)key.c_str());
#elif defined(CEF_STRING_TYPE_UTF8)
            QString qKey = QString::fromUtf8(key.c_str());
#elif defined(CEF_STRING_TYPE_WIDE)
            QString qKey = QString::fromWCharArray(key.c_str());
#endif
            qDict.insert(qKey, qVal);
        }
        variant.setValue(qDict);
    }
    break;
    case CefValueType::VTYPE_LIST:
    {
        auto cList = cefValue->GetList();
        auto cCount = cList->GetSize();
        QVariantList qList;
        for (int i = 0; i < cCount; i++)
        {
            auto cVal = cList->GetValue(i);
            QVariant qVal;
            CefValueToQVariant(qVal, cVal);
            qList.push_back(qVal);
        }
        variant.setValue(qList);
    }
    break;
    default:
        qWarning() << "Unsupported CefValueType conversion: " << type;
    }
}


void QVariantToCefValue(CefRefPtr<CefValue> & cefValue, const QVariant & variant)
{
    if (cefValue == nullptr)
    {
        qWarning() << "Invalid arguments";
        return;
    }

#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
    auto type = variant.type();
    #define TypeClass       QVariant
    #define UndefinedType   Invalid
    #define StringType      String
    #define ByteArrayType   ByteArray
    #define VariantMapType  Map
    #define VariantListType List
#else
    auto type = variant.typeId();
    #define TypeClass       QMetaType
    #define UndefinedType   UnknownType
    #define StringType      QString
    #define ByteArrayType   QByteArray
    #define VariantMapType  QVariantMap
    #define VariantListType QVariantList
#endif

    switch (type)
    {
    case TypeClass::UndefinedType:
    {
        cefValue->SetNull();
    }
    break;
    case TypeClass::Bool:
    {
        cefValue->SetBool(variant.toBool());
    }
    break;
    case TypeClass::Char:
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    case TypeClass::UChar:
    case TypeClass::Short:
    case TypeClass::UShort:
#endif
    case TypeClass::Int:
    {
        cefValue->SetInt(variant.toInt());
    }
    break;
    case TypeClass::UInt:
    case TypeClass::LongLong:
    case TypeClass::ULongLong:
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    case TypeClass::Float:
#endif
    case TypeClass::Double:
    {
        cefValue->SetDouble(variant.toDouble());
    }
    break;
    case TypeClass::StringType:
    {
        CefString cStr = CefString(variant.toString().toStdString());
        cefValue->SetString(cStr);
    }
    break;
    case TypeClass::ByteArrayType:
    {
        auto qData = variant.toByteArray();
        auto cData = CefBinaryValue::Create(qData.constData(), qData.size());
        cefValue->SetBinary(cData);
    }
    break;
    case TypeClass::VariantMapType:
    {
        auto qDict = variant.toMap();
        auto keys = qDict.keys();
        auto cDict = CefDictionaryValue::Create();
        for (auto& key : keys)
        {
            auto qVal = qDict.take(key);
            auto cVal = CefValue::Create();
            QVariantToCefValue(cVal, qVal);
            CefString cKey = CefString(key.toStdString());
            cDict->SetValue(cKey, cVal);
        }
        cefValue->SetDictionary(cDict);
    }
    break;
    case TypeClass::VariantListType:
    {
        auto qList = variant.toList();
        auto qCount = qList.size();
        auto cList = CefListValue::Create();
        for (int i = 0; i < qCount; i++)
        {
            auto qVal = qList.at(i);
            auto cVal = CefValue::Create();
            QVariantToCefValue(cVal, qVal);
            cList->SetValue(i, cVal);
        }
        cefValue->SetList(cList);
    }
    break;
    default:
        qWarning() << "Unsupported QVariantType conversion: " << type;
    break;
    }
}


static uint32_t winGetKeyboardModifiers(QKeyEvent* qe)
{
    auto cm = 0;
    auto m = qe->modifiers();
    cm |= m & Qt::ShiftModifier ? EVENTFLAG_SHIFT_DOWN : 0;
    cm |= m & Qt::AltModifier ? EVENTFLAG_ALT_DOWN : 0;
    cm |= m & Qt::KeypadModifier ? EVENTFLAG_IS_KEY_PAD : 0;
    cm |= m & Qt::ControlModifier ? EVENTFLAG_CONTROL_DOWN : 0;

    if (::GetKeyState(VK_NUMLOCK) & 1)
        cm |= EVENTFLAG_NUM_LOCK_ON;

    if (::GetKeyState(VK_CAPITAL) & 1)
        cm |= EVENTFLAG_CAPS_LOCK_ON;

    auto vk = qe->nativeVirtualKey();
    switch (vk)
    {
    case VK_SHIFT:
        if (::GetKeyState(VK_LSHIFT) & 0x8000)
            cm |= EVENTFLAG_IS_LEFT;
        else if (::GetKeyState(VK_RSHIFT) & 0x8000)
            cm |= EVENTFLAG_IS_RIGHT;
        break;
    case VK_CONTROL:
        if (::GetKeyState(VK_LCONTROL) & 0x8000)
            cm |= EVENTFLAG_IS_LEFT;
        else if (::GetKeyState(VK_RCONTROL) & 0x8000)
            cm |= EVENTFLAG_IS_RIGHT;
        break;
    case VK_MENU:
        if (::GetKeyState(VK_LMENU) & 0x8000)
            cm |= EVENTFLAG_IS_LEFT;
        else if (::GetKeyState(VK_RMENU) & 0x8000)
            cm |= EVENTFLAG_IS_RIGHT;
        break;
    case VK_LWIN:
        cm |= EVENTFLAG_IS_LEFT;
        break;
    case VK_RWIN:
        cm |= EVENTFLAG_IS_RIGHT;
        break;
    }

       // mimic alt-gr check behavior from
    if (!qe->text().isEmpty() && (::GetKeyState(VK_RMENU) & 0x8000))
    {
        // reverse AltGr detection taken from PlatformKeyMap::UsesAltGraph
        // instead of checking all combination for ctrl-alt, just check current char
        HKL current_layout = ::GetKeyboardLayout(0);

        // https://docs.microsoft.com/en-gb/windows/win32/api/winuser/nf-winuser-vkkeyscanexw
        // ... high-order byte contains the shift state,
        // which can be a combination of the following flag bits.
        // 2 Either CTRL key is pressed.
        // 4 Either ALT key is pressed.
        SHORT scan_res = ::VkKeyScanExW(vk, current_layout);
        if (((scan_res >> 8) & 0xFF) == (2 | 4))
        {
            // ctrl-alt pressed
            cm &= ~(EVENTFLAG_CONTROL_DOWN | EVENTFLAG_ALT_DOWN);
            cm |= EVENTFLAG_ALTGR_DOWN;
        }
    }

    return cm;
}


static void winQKeyEventToCefKeyEvent(QKeyEvent* qe, CefKeyEvent& ce)
{
    ce.windows_key_code = qe->nativeVirtualKey();
    ce.modifiers = winGetKeyboardModifiers(qe);

    if (ce.modifiers & EVENTFLAG_ALT_DOWN)
        ce.is_system_key = true;
}

void MapQKeyEventToCefKeyEvent(QKeyEvent* qe, CefKeyEvent& ce)
{
#if defined(Q_OS_MACOS)
    macQKeyEventToCefKeyEvent(qe, ce);
#elif defined(Q_OS_WINDOWS)
    winQKeyEventToCefKeyEvent(qe, ce);
#elif defined(Q_OS_LINUX)
    linuxQKeyEventToCefKeyEvent(qe, ce);
#endif
}

bool ShouldSendKeyCharEvent(QKeyEvent* qe)
{
#if defined(Q_OS_MACOS)
    return macShouldSendKeyCharEvent(qe);
#else
    return !qe->text().isEmpty();
#endif
}

void AdjustCefKeyCharEvent(QKeyEvent* qe, CefKeyEvent& ce)
{
#if defined(Q_OS_MACOS)
    return;
#else
    if (!qe->text().isEmpty())
    {
        ce.windows_key_code = qe->text().at(0).unicode();
        ce.unmodified_character = qe->key();
        ce.character = ce.windows_key_code;
    }
#endif
}


//type: cef_cursor_type_t
Qt::CursorShape mapCursorShape(int type)
{
    static Qt::CursorShape sCursorTable[] = {
        Qt::ArrowCursor,        // CT_POINTER = 0,
        Qt::CrossCursor,        // CT_CROSS,
        Qt::PointingHandCursor, // CT_HAND,
        Qt::IBeamCursor,        // CT_IBEAM,
        Qt::WaitCursor,         // CT_WAIT,
        Qt::WhatsThisCursor,    // CT_HELP,
        Qt::SizeHorCursor,      // CT_EASTRESIZE,
        Qt::SizeVerCursor,      // CT_NORTHRESIZE,
        Qt::SizeBDiagCursor,    // CT_NORTHEASTRESIZE,
        Qt::SizeFDiagCursor,    // CT_NORTHWESTRESIZE,
        Qt::SizeVerCursor,      // CT_SOUTHRESIZE,
        Qt::SizeFDiagCursor,    // CT_SOUTHEASTRESIZE,
        Qt::SizeBDiagCursor,    // CT_SOUTHWESTRESIZE,
        Qt::SizeHorCursor,      // CT_WESTRESIZE,
        Qt::SizeVerCursor,      // CT_NORTHSOUTHRESIZE,
        Qt::SizeHorCursor,      // CT_EASTWESTRESIZE,
        Qt::SizeAllCursor,      // CT_NORTHEASTSOUTHWESTRESIZE,
        Qt::SizeAllCursor,      // CT_NORTHWESTSOUTHEASTRESIZE,
        Qt::SplitHCursor,       // CT_COLUMNRESIZE,
        Qt::SplitVCursor,       // CT_ROWRESIZE,
        Qt::ArrowCursor,        // CT_MIDDLEPANNING,
        Qt::ArrowCursor,        // CT_EASTPANNING,
        Qt::ArrowCursor,        // CT_NORTHPANNING,
        Qt::ArrowCursor,        // CT_NORTHEASTPANNING,
        Qt::ArrowCursor,        // CT_NORTHWESTPANNING,
        Qt::ArrowCursor,        // CT_SOUTHPANNING,
        Qt::ArrowCursor,        // CT_SOUTHEASTPANNING,
        Qt::ArrowCursor,        // CT_SOUTHWESTPANNING,
        Qt::ArrowCursor,        // CT_WESTPANNING,
        Qt::ArrowCursor,        // CT_MOVE,
        Qt::ArrowCursor,        // CT_VERTICALTEXT,
        Qt::ArrowCursor,        // CT_CELL,
        Qt::ArrowCursor,        // CT_CONTEXTMENU,
        Qt::ArrowCursor,        // CT_ALIAS,
        Qt::ArrowCursor,        // CT_PROGRESS,
        Qt::ArrowCursor,        // CT_NODROP,
        Qt::ArrowCursor,        // CT_COPY,
        Qt::ArrowCursor,        // CT_NONE,
        Qt::ForbiddenCursor,    // CT_NOTALLOWED,
        Qt::ArrowCursor,        // CT_ZOOMIN,
        Qt::ArrowCursor,        // CT_ZOOMOUT,
        Qt::OpenHandCursor,     // CT_GRAB,
        Qt::ClosedHandCursor,   // CT_GRABBING,
        Qt::ArrowCursor,        // CT_MIDDLE_PANNING_VERTICAL,
        Qt::ArrowCursor,        // CT_MIDDLE_PANNING_HORIZONTAL,
        Qt::ArrowCursor,        // CT_CUSTOM,
        Qt::ArrowCursor,        // CT_DND_NONE,
        Qt::DragMoveCursor,     // CT_DND_MOVE,
        Qt::DragCopyCursor,     // CT_DND_COPY,
        Qt::DragLinkCursor,     // CT_DND_LINK,
    };

    return sCursorTable[type];
}



QMultiMap<QString, QString> CefMapToQMap(const std::multimap<CefString, CefString> & mp)
{
    QMultiMap<QString, QString> ret;

    for (auto iter = mp.begin(); iter != mp.end(); iter++)
    {
        ret.insert(CefStrToQStr(iter->first), CefStrToQStr(iter->second));
    }

    return ret;
}

std::multimap<CefString, CefString> QMapToCefMap(const QMultiMap<QString, QString> & mp)
{
    std::multimap<CefString, CefString> ret;

    for (auto iter = mp.begin(); iter != mp.end(); iter++)
    {
        ret.insert(std::make_pair(QStrToCefStr(iter.key()), QStrToCefStr(iter.value())));
    }

    return ret;
}
